package com.gylang.barcode;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.EncodeHintType;
import com.google.zxing.MultiFormatWriter;
import com.google.zxing.WriterException;
import com.google.zxing.client.j2se.MatrixToImageConfig;
import com.google.zxing.client.j2se.MatrixToImageWriter;
import com.google.zxing.common.BitMatrix;
import com.gylang.barcode.domain.BarFieldWrapper;
import com.gylang.barcode.domain.BarcodeWrapper;
import com.gylang.barcode.enums.Position;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.WritableRaster;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URL;
import java.util.List;
import java.util.Map;

/**
 * @author gylang
 * data 2021/6/5
 */
public class ImageUtil {


	/**
	 * 创建条形码
	 *
	 * @param wrapper qrcode包装类
	 * @return qrcode buffer
	 * @throws IOException
	 * @throws WriterException
	 */
	public static BufferedImage createQrcode(BarcodeWrapper wrapper) throws IOException, WriterException {

		wrapper.setBoxWith(wrapper.getBoxWith() <= 0 ? wrapper.getBarcodeWith() : wrapper.getBoxWith());
		wrapper.setBoxHeight(wrapper.getBoxHeight() <= 0 ? wrapper.getBarcodeHeight() : wrapper.getBoxHeight());
		String bgUrl = wrapper.getBackground();

		BufferedImage baseImg = null;
		baseImg = new BufferedImage(wrapper.getBoxWith(), wrapper.getBoxHeight(), wrapper.getRgbType());
		// 创建绘图对象
		Graphics2D baseGraphics = baseImg.createGraphics();
		if (StrUtil.isNotEmpty(bgUrl)) {
			BufferedImage background = ImageIO.read(new URL(bgUrl));
			drawImage(baseGraphics, background, 0, 0, wrapper.getBoxWith(), wrapper.getBoxHeight(), wrapper.isScaling());
		}

		// 生成条形码
		BufferedImage barcode = createQrcode(wrapper.getContent(), wrapper.getBarOnColor(), wrapper.getBarOffColor(), wrapper.isRemoveWhiteEdge(),
			wrapper.getBarcodeWith(), wrapper.getBarcodeHeight(), wrapper.getHintType(), wrapper.getType());


		// 设置条形码透明度
		drawBarCodeImg(barcode, baseGraphics, wrapper);

		// 合成其他图片
		drawImageList(baseGraphics, wrapper, wrapper.getImgWrappers());

		// 合成文字
		drawText(baseGraphics, wrapper, wrapper.getTextWrappers());
		return baseImg;
	}

	/**
	 * 创建条形码
	 *
	 * @param content  条形码内容
	 * @param onColor  命中数值颜色 (条形码颜色)
	 * @param offColor 非命中颜色 (背景样色)
	 * @param width    宽度
	 * @param height   长度
	 * @param hintType 生成条形码参数
	 * @return 条形码
	 * @throws WriterException
	 */
	public static BufferedImage createQrcode(String content, int onColor, int offColor, boolean removeWhiteEdge,
											 int width, int height, Map<EncodeHintType, Object> hintType, BarcodeFormat barcodeFormat) throws WriterException {
		BitMatrix bitMatrix = new MultiFormatWriter().encode(content, barcodeFormat, width, height, hintType);
		if (removeWhiteEdge) {
			bitMatrix = removeWhiteEdge(bitMatrix);
		}
		MatrixToImageConfig matrixToImageConfig = new MatrixToImageConfig(onColor, offColor);

		return MatrixToImageWriter.toBufferedImage(bitMatrix, matrixToImageConfig);
	}

	/**
	 * 渲染text文本
	 *
	 * @param baseGraphics 绘制对象
	 * @param wrapper      条形码包装类
	 * @param textWrappers 文本包装类
	 */
	public static void drawText(Graphics2D baseGraphics, BarcodeWrapper wrapper, List<BarFieldWrapper> textWrappers) {

		if (CollUtil.isNotEmpty(textWrappers)) {
			for (BarFieldWrapper textWrapper : textWrappers) {
				baseGraphics.setComposite(textWrapper.getComposite());
				drawText(baseGraphics, textWrapper.getColor(), textWrapper.getBackground(), textWrapper.getFont(), textWrapper.getContent(),
					textWrapper.getX(), textWrapper.getY(), wrapper.getBoxWith(), wrapper.getBoxHeight(), wrapper.getBoxMargin(), textWrapper.getPosition());
			}
		}


	}

	/**
	 * 渲染文本
	 *
	 * @param baseGraphics 绘制对象
	 * @param color        颜色
	 * @param background   背景
	 * @param font         字体
	 * @param content      内容
	 * @param x            x
	 * @param y            y
	 * @param width        宽
	 * @param margin       内边距
	 * @param position     定位类型
	 */
	public static void drawText(Graphics2D baseGraphics, Color color, Color background, Font font, String content,
								int x, int y, int width, int height, int margin, Position position) {
		baseGraphics.setColor(color);
		if (null != background) {
			baseGraphics.setBackground(background);
		}
		baseGraphics.setFont(font);
		int stringWidth = baseGraphics.getFontMetrics().stringWidth(content);
		int leftX = getLeftX(width, x, margin, stringWidth, position);
		// 没有处理换行
		int stringHeight = baseGraphics.getFontMetrics().getHeight() / 2;
		int topY = getTopY(height, y, margin, stringHeight, position);
		baseGraphics.drawString(content, leftX, topY);
	}

	/**
	 * 绘制图片
	 *
	 * @param baseGraphics 绘制对象
	 * @param wrapper      条形码包装类
	 * @param imgList      图片列表
	 * @throws IOException
	 */
	public static void drawImageList(Graphics2D baseGraphics, BarcodeWrapper wrapper, List<BarFieldWrapper> imgList) throws IOException {
		if (CollUtil.isNotEmpty(imgList)) {
			for (BarFieldWrapper fieldWrapper : imgList) {
				BufferedImage bufferedImage = ImageIO.read(new URL(fieldWrapper.getContent()));
				baseGraphics.setComposite(fieldWrapper.getComposite());
				drawBarImg(bufferedImage, baseGraphics, wrapper.getBoxWith(), wrapper.getBoxHeight(), wrapper.getBoxMargin(),
					fieldWrapper.getWidth(), fieldWrapper.getHeight(), fieldWrapper.getPosition(), fieldWrapper.getX(), fieldWrapper.getY(), fieldWrapper.isScaling());
			}
		}
	}

	/**
	 * 渲染条形码基本图形
	 *
	 * @param image        条形码对象
	 * @param baseGraphics 绘制对象
	 * @param wrapper      条形码包装类
	 */
	public static void drawBarCodeImg(BufferedImage image, Graphics2D baseGraphics, BarcodeWrapper wrapper) {

		baseGraphics.setComposite(wrapper.getComposite());
		//设置 图片合成参数

		int insertWidth = image.getWidth();
		int insertHeight = image.getHeight();
		int barcodeMargin = wrapper.getBarcodeMargin();
		int imgWith = insertWidth + (2 * barcodeMargin);
		int imgHeight = insertHeight + (2 * barcodeMargin);
		if (barcodeMargin > 0) {
			// 创建新的渲染模板
			BufferedImage bufferedImage = new BufferedImage(imgWith, imgHeight, BufferedImage.TYPE_INT_RGB);
			drawImage(bufferedImage.createGraphics(), image, barcodeMargin, barcodeMargin, insertWidth, insertHeight, wrapper.isScaling());
			// 重新设置内边距
			for (int i = 0; i < imgWith; i++) {

				for (int j = 0; j < imgHeight; j++) {
					if (i < barcodeMargin || i >= barcodeMargin + insertWidth || j < barcodeMargin || j >= barcodeMargin + insertHeight) {
						bufferedImage.setRGB(i, j, wrapper.getBarOffColor());
					} else {
						// 移动长度为条形码宽度 因为离开填充区 要么换行/ 要么就是另一边的填充区(位置刚好移动宽度 `-1 是因为for自动加了1`)
						j += insertHeight - 1;
					}
				}
			}
			// 使用的新的条形码
			image = bufferedImage;
		}
		drawBarImg(image, baseGraphics, wrapper.getBoxWith(), wrapper.getBoxHeight(), wrapper.getBoxMargin(), imgWith, imgHeight,
			wrapper.getPosition(), wrapper.getX(), wrapper.getY(), wrapper.isScaling());
	}


	/**
	 * 渲染条形码基本图形
	 *
	 * @param image        条形码对象
	 * @param baseGraphics 绘制对象
	 * @param width        宽
	 * @param height       高
	 * @param margin       内边距
	 * @param position     定位类型
	 * @param x            x
	 * @param y            y
	 */
	public static void drawBarImg(BufferedImage image, Graphics2D baseGraphics, int width, int height, int margin, int insertWidth, int insertHeight,
								  Position position, int x, int y, boolean scaling) {
		// 获取x左边
		if (scaling) {
			double sx = (double) insertWidth / image.getWidth();
			double sy = (double) insertHeight / image.getHeight();
			ColorModel cm = image.getColorModel();
			WritableRaster raster = cm.createCompatibleWritableRaster(insertWidth, insertHeight);
			boolean alphaPremultiplied = cm.isAlphaPremultiplied();

			BufferedImage target = new BufferedImage(cm, raster, alphaPremultiplied, null);
			Graphics2D g = target.createGraphics();
			// smoother than exlax:
			g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
			g.drawRenderedImage(image, AffineTransform.getScaleInstance(sx, sy));
			g.dispose();
			image = target;
		}
		int leftX = getLeftX(width, x, margin, insertWidth, position);
		int topY = getTopY(height, y, margin, insertHeight, position);

		drawImage(baseGraphics, image, leftX, topY, insertWidth, insertHeight, scaling);
	}

	/**
	 * 绘制图片
	 *
	 * @param baseGraphics 绘制对象
	 * @param image        图片对象
	 * @param x            x轴
	 * @param y            y轴
	 * @param width        宽度
	 * @param height       高度
	 */
	public static void drawImage(Graphics2D baseGraphics, BufferedImage image, int x, int y, int width, int height, boolean scaling) {

		baseGraphics.drawImage(image, x, y, width, height, null);

	}

	/**
	 * 计算y轴起始左边
	 *
	 * @param height       高度
	 * @param y            y
	 * @param margin       内边距
	 * @param insertHeight 插入组件高度
	 * @param position     定位类型
	 * @return y轴坐标
	 */
	private static int getTopY(int height, int y, int margin, int insertHeight, Position position) {

		return Position.CENTER.equals(position) || Position.VERTICAL_CENTER.equals(position) ? (height - insertHeight - margin) / 2 + margin : y;
	}

	/**
	 * 计算x轴起始左边
	 *
	 * @param width       宽度
	 * @param x           x
	 * @param margin      内边距
	 * @param insertWidth 插入组件宽度
	 * @param position    定位类型
	 * @return x轴坐标
	 */
	public static int getLeftX(int width, int x, int margin, int insertWidth, Position position) {


		if (Position.ABSOLUTE.equals(position)) {

			// x = left-margin + begin-X
			return margin + x;
		} else if (Position.LEFT.equals(position)) {

			// x = box-left-margin +  left-margin  + begin-x
			return margin + x;
		} else if (Position.CENTER.equals(position) || Position.HOR_CENTER.equals(position)) {
			// x =  box-width - (字符长) - box-left-margin text-margin-left - text-with
			return (width - insertWidth - margin) / 2 + margin;
		} else {

			// x = box-left-margin +  left-margin  + begin-x
			return width - margin - insertWidth - x;
		}
	}


	/**
	 * 删除白边
	 */
	public static BitMatrix removeWhiteEdge(BitMatrix matrix) {
		int[] rec = matrix.getEnclosingRectangle();
		int resWidth = rec[2] + 1;
		int resHeight = rec[3] + 1;

		BitMatrix resMatrix = new BitMatrix(resWidth, resHeight);
		resMatrix.clear();
		for (int i = 0; i < resWidth; i++) {
			for (int j = 0; j < resHeight; j++) {
				if (matrix.get(i + rec[0], j + rec[1])) {
					resMatrix.set(i, j);
				}
			}
		}
		return resMatrix;
	}

	/**
	 * 保存图片
	 *
	 * @param image  图片对象
	 * @param suffix 扩张名
	 * @param path   路径
	 * @throws IOException
	 */
	public static void save(BufferedImage image, String suffix, String path) throws IOException {
		image.flush();
		ImageIO.write(image, suffix, new File(path));
	}

	/**
	 * 保存图片
	 *
	 * @param image        图片对象
	 * @param suffix       扩张名
	 * @param outputStream 输出流
	 * @throws IOException
	 */
	public static void save(BufferedImage image, String suffix, OutputStream outputStream) throws IOException {
		image.flush();
		ImageIO.write(image, suffix, outputStream);
	}
}
